#!/usr/bin/env python3
# -*- coding:utf-8 -*-

import os
import sys
import re
import random

import pygraphviz as pgv

import ctools.gvhelper.colors as gvcolors

from ctools.common.paramconfig import ParamConfig

from ctools.logext import *

_rlog, _plog, _mlog, _tlog = get_loggers(__file__, __name__, propagate=False)

class GvRender():

    edge_render_modes = (
        'head', 'headport', 'headfull',
        'tail', 'tailport', 'tailfull',
        'random', 'none',
    )

    def __init__(self):
        self.mode = 'tailport'

        self.edge_color_min = 10
        self.edge_color_max = 150

        self.node_color_min = 180
        self.node_color_max = 230

        self.cluster_color_min = 230
        self.cluster_color_max = 250

        table_pattern = r'table +bgcolor=\"[\w ]+\"'
        td_pattern = r'td +bgcolor=\"[\w ]+\"'
        self.tbl_r = re.compile(table_pattern)
        self.td_r = re.compile(td_pattern)

    def update_params(self, paramcfg:ParamConfig):
        self.mode           = paramcfg.get_str("MODE", "tailport")
        self.node_color_min = paramcfg.get_int("NODE_COLOR_MIN", 180)
        self.node_color_max = paramcfg.get_int("NODE_COLOR_MAX", 230)
        self.edge_color_min = paramcfg.get_int("EDGE_COLOR_MIN", 10)
        self.edge_color_max = paramcfg.get_int("EDGE_COLOR_MAX", 150)
        self.cluster_color_min = paramcfg.get_int("CLUSTER_COLOR_MIN", 230)
        self.cluster_color_max = paramcfg.get_int("CLUSTER_COLOR_MAX", 250)

    @classmethod
    def get_str(cls, edge, mode='head'):
        try:
            if mode not in cls.edge_render_modes:
                return str(edge)

            if mode == 'random':
                return str(random.random())

            left, right = edge[0], edge[1]

            if 'dir' in edge.attr:
                if 'back' == edge.attr['dir']:
                    left, right = right, left

            if mode == 'head':  #  a  -> [b]
                return right
            if mode == 'tail':  # [a] ->  b
                return left

            if mode == 'headfull':
                tmp = ''
                try:
                    tmp = edge.attr['headport']
                finally:
                    return f'{right}{tmp}'

            if mode == 'tailfull':
                tmp = ''
                try:
                    tmp = edge.attr['tailport']
                finally:
                    return f'{left}{tmp}'

            # process headport and tailport start
            if edge.attr[mode] and len(edge.attr[mode]) > 0:
                return edge.attr[mode]

            if mode == 'headport':
                return right
            if mode == 'tailport':
                return left
            # process headport and tailport end

        except:
            return str(edge)

    @classmethod
    def _render_sg(cls, sgs, clist):
        '''render subgraph'''
        for sg in sgs:
            bgcolor = gvcolors.random_color(sg.to_string(), clist=clist)
            sg.graph_attr.update(bgcolor=bgcolor)
            cls._render_sg(sg.subgraphs(), clist)

    def _add_graph_comment(self, graph:pgv.AGraph, force=False, **kwargs):
        comments = []
        comments.append('mode: {}'.format(self.mode))
        comments.append('node: {}~{}'.format(self.node_color_min, self.node_color_max))
        comments.append('edge: {}~{}'.format(self.edge_color_min, self.edge_color_max))
        comments.append('subgraph: {}~{}'.format(self.cluster_color_min, self.cluster_color_max))

        for k, v in kwargs.items():
            comments.append('{}: {}'.format(k, v))

        graph.graph_attr['comment'] = ', '.join(comments)

    def _render_node(self, graph:pgv.AGraph, force=False):
        min_gray = self.node_color_min
        max_gray = self.node_color_max

        clist = gvcolors.colors_by_gray(vmin=min_gray, vmax=max_gray)
        clist2 = gvcolors.colors_by_gray(vmin=min_gray - 10, vmax=max_gray - 10)

        for node in graph.nodes():
            color = gvcolors.random_color(node, clist=clist)
            color2 = gvcolors.random_color(node, clist=clist2)
            label = node.attr['label']
            if label and '</table>' in label:   # html
                bgc_tbl = self.tbl_r.findall(label)
                bgc_td = self.td_r.findall(label)
                if bgc_tbl:
                    orig = bgc_tbl[0]
                    label = label.replace(orig, 'table bgcolor="{}"'.format(color2), 1)
                else:
                    orig = 'table'
                    label = label.replace(orig, 'table bgcolor="{}"'.format(color2), 1)

                if bgc_td:
                    orig = bgc_td[0]
                    label = label.replace(orig, 'td bgcolor="{}"'.format(color), 1)
                else:
                    orig = 'td'
                    label = label.replace(orig, 'table bgcolor="{}"'.format(color), 1)
                # FIXME: just do at GvCParser
                # node.attr.update(label=label)     # work wrong
                # node.attr.['label'] = label       # work wrong
                # setattr(node.attr, 'label', label)  # do not work
            else:
                node.attr.update(style='filled')
                node.attr.update(color=color)

    def _render_edge(self, graph:pgv.AGraph, force=False):
        min_gray = self.edge_color_min
        max_gray = self.edge_color_max

        clist = gvcolors.colors_by_gray(vmin=min_gray, vmax=max_gray)
        for edge in graph.edges():
            tmp = self.get_str(edge, self.mode)
            _mlog.debug('edge str for color: {}'.format(tmp))
            color = gvcolors.random_color(tmp, clist=clist)
            edge.attr.update(color=color)

    def _render_subgraph(self, graph:pgv.AGraph, force=False):
        min_gray = self.cluster_color_min
        max_gray = self.cluster_color_max

        clist = gvcolors.colors_by_gray(vmin=min_gray, vmax=max_gray)
        self._render_sg(graph.subgraphs(), clist)

    def do_render(self, graph:pgv.AGraph, mode:str="", force=False) -> None:
        '''
        mode: ""/default/all node|edge|subgraph
        '''
        if mode == "default":
            mode = 'node|edge|subgraph'
        elif mode == 'all':
            mode = 'node|edge|subgraph'


        self._add_graph_comment(graph, force, render_mode=mode)

        tokens = mode.split('|')
        for element in tokens:
            element = element.strip()
            funcname = f"_render_{element}"
            if not hasattr(self, funcname):
                _plog.warning(f"{funcname} is not found")
                continue
            func = getattr(self, funcname)
            func(graph, force)
